package sketch.specs;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;

import sketch.ast.ASTUtils;
import sketch.main.SketchTestOptions;
import sketch.main.TestSketchValidator;
import sketch.specs.annotation.AnnotationMethodProcessor;
import sketch.specs.annotation.Repl;
import sketch.specs.annotation.Repls;
import sketch.specs.annotation.TestSketch;
import sketch.util.Checker;

public final class SketchTestProcessor {
	
	public static boolean debug = true;

	public static String expand_method_name = "expand";
	public final String sourcePath;
	public final String fileNameWithoutSuffix;
	public final String fileName;
	//public final String packageName;
	
	//private CompilationUnit cached = null;
	
	public SketchTestProcessor(String sourcePath) {
		Checker.checkNull(sourcePath, "The source path could not be null.");
		Checker.checkTrue(sourcePath.endsWith(".java"), "The source does not end with .java, is illegal: " + sourcePath);
		this.sourcePath = sourcePath;
		//remove the .java part
		this.fileName = (new File(this.sourcePath)).getName();
		this.fileNameWithoutSuffix = fileName.substring(0, fileName.indexOf(".java"));
		
		//check the validity of the test sketch
		TestSketchValidator validator = new TestSketchValidator(this.sourcePath);
		String[] error_msgs = validator.validate();
		if(error_msgs.length != 0) {
			for(String msg : error_msgs) {
				System.out.println(msg);
			}
			System.exit(1);
		}
	}
	
	public String getFileNameWithoutSuffix() {
		return this.fileNameWithoutSuffix;
	}
	
	public String getPackageName() {
		//if(cached == null) {
		CompilationUnit	cached = getProcessedCompilationUnit();
		//}
		return cached.getPackage().getName().toString();
	}
	
	public CompilationUnit getProcessedCompilationUnit() {
		SketchedMethodsCreator creator = new SketchedMethodsCreator(this.sourcePath);
		
		//get a list of expanded method, including those generated by replacement
		Map<MethodDeclaration, List<MethodDeclaration>> method_expansions
		    = new LinkedHashMap<MethodDeclaration, List<MethodDeclaration>>();
		
		//create a list of sketched method declaration
		List<SketchedMethodDeclaration> sketched_methods = creator.createSketchedMethodList();
		
		if(!creator.sketchedBlocks.isEmpty() && SketchTestOptions.verbose) {
			System.out.println("  - has annotations ... ");
		}
		
		for(SketchedMethodDeclaration sketchedMethod : sketched_methods) {
//			if(sketchedMethod.method_declaration.getName().toString().equals("testDecorator")) {
//				System.out.println();
//			}
			List<MethodDeclaration> methods = sketchedMethod.getMethodsAfterExpansion();
			//the expanded method list
			//there is at least one method
			Checker.checkTrue(methods.size() >= 1, "The method size: " + methods.size() + " is not correct, when processing: "
					+ sketchedMethod.method_declaration.getName());
			List<MethodDeclaration> expanded_method_list = new LinkedList<MethodDeclaration>();
			int count = 0;
			//System.out.println("method size: " + methods.size());
			for(int i = 0; i < methods.size(); i++) { //XXX i = 0 or 1, for replacement or expansion?
				MethodDeclaration method = methods.get(i);
//				
				//copy the modifier
				for(Object o : sketchedMethod.method_declaration.modifiers()) {
					IExtendedModifier modifier = (IExtendedModifier)o;
					if(modifier.isAnnotation()) {
						Annotation ann = (Annotation)modifier;
						ann = ASTUtils.deepClone(ann);
						ann.delete();
						method.modifiers().add(ann);
					}
				}
				
				String methodName = method.getName().toString();
//				if(methodName.indexOf(SketchedMethodDeclaration.replic_sep) == -1) {
//					methodName = methodName + SketchedMethodDeclaration.replic_sep;
//				}
				method.getName().setIdentifier(methodName + "_" + expand_method_name + "_" + (count++));
				List<MethodDeclaration> repls = this.create_replaced_methods(method);
				//System.out.println("size of repls: " + repls.size());
				expanded_method_list.add(method);
				expanded_method_list.addAll(repls);
			}
			//if there is no methods been expanded
//			if(methods.size() == 1) {
//				expanded_method_list.addAll(this.create_replaced_methods(sketchedMethod.method_declaration));
//			}
			//remove all annotations from the sketch framework
			ASTUtils.removeMethodAnnotation(sketchedMethod.method_declaration, TestSketch.class);
			ASTUtils.removeMethodAnnotation(sketchedMethod.method_declaration, Repls.class);
			ASTUtils.removeMethodAnnotation(sketchedMethod.method_declaration, Repl.class);
			for(MethodDeclaration method : expanded_method_list) {
				ASTUtils.removeMethodAnnotation(method, TestSketch.class);
				ASTUtils.removeSpecValue(method);
				ASTUtils.makeMethodPublic(method);
				ASTUtils.addOUnitAnnotation(method);
				//do not miss the exception throwing
				method.thrownExceptions().clear();
				if(!sketchedMethod.method_declaration.thrownExceptions().isEmpty()) {
					List<Name> exceptions = sketchedMethod.method_declaration.thrownExceptions();
					for(Name exception : exceptions) {
						Name copy_exception = ASTUtils.deepClone(exception);
						copy_exception.delete();
						method.thrownExceptions().add(copy_exception);
					}
				}
			}
			
			method_expansions.put(sketchedMethod.method_declaration, expanded_method_list);
		}
		
		//construct the compilation unit here
		CompilationUnit unit = creator.getUnit();
		AppendExpandedMethodVisitor visitor = new AppendExpandedMethodVisitor(unit, method_expansions);
		visitor.visit(unit);
		
		String unit_txt = visitor.getResult();
		CompilationUnit newUnit = ASTUtils.parseUnit(unit_txt);
		
		return newUnit;
	}
	
	private List<MethodDeclaration> create_replaced_methods(MethodDeclaration method) {
		List<MethodDeclaration> repaced_methods = new LinkedList<MethodDeclaration>();
		
		//for(MethodDeclaration method : methods) {
			AnnotationMethodProcessor processor = new AnnotationMethodProcessor(method);
			List<Map<String, String>> mapping_list = processor.getMappings();
			MethodRepInstanitiations repl_instan = new MethodRepInstanitiations(method, mapping_list);
			repaced_methods.addAll(repl_instan.getReplacedMethods());
		//}
		
		return repaced_methods;
	}
	
//	private boolean hasSketchedNotations(List<SketchedMethodDeclaration> sketched_method_list) {
//		boolean has_notation = false;
//		for(SketchedMethodDeclaration sketched_method : sketched_method_list) {
//			if(sketched_method.)
//		}
//	}
}